Certificates, Trust & Authentication

What a cert is · Chain of trust · How to get one · Where it lives · Lifecycle · First connection · How auth works · mTLS

1 · What is a Cert?
2 · Chain of Trust
3 · How to Get One
4 · Where It Lives
5 · Lifecycle
6 · First Connection
7 · How Auth Works
8 · mTLS & Internal

A certificate is a signed statement: "This public key belongs to api.example.com"

Issued and cryptographically signed by a Certificate Authority your OS already trusts. Click each section to expand.

Certificate anatomy — click to expand
📋 Subject — who this cert is FOR
Common Nameapi.example.com
SANapi.example.com, *.example.com
OrganisationExample Corp Ltd
SAN is what browsers check. CN alone is deprecated. Wildcard *.example.com covers all subdomains.
🔑 Public Key — the server's identity key
AlgorithmECDSA P-256
Key size256 bit (equivalent to RSA-3072)
Value04:a3:f1:2e:9c:... (65 bytes)
Public key is shared with everyone. The matching private key never leaves the server. The cert binds this key to the domain.
🏛️ Issuer — who SIGNED this cert
Issuer CNLet's Encrypt R10
Signature algECDSA with SHA-384
Signature3046:02:21:00:f4:... (71 bytes)
Let's Encrypt used its private key to sign a hash of this cert. Anyone can verify using Let's Encrypt's public key (which is in their intermediate cert).
📅 Validity Period
Not Before2024-01-01 00:00:00 UTC
Not After2024-04-01 00:00:00 UTC
Duration90 days (Let's Encrypt default)
Short TTL limits damage if private key is stolen. certbot auto-renews at day 60.
🔒 Extensions
Extended Key UsageTLS Web Server Authentication
CA:FALSEThis cert cannot sign other certs
OCSP URLhttp://r10.o.lencr.org
CRL URLhttp://r10.c.lencr.org
What a cert is and is NOT
❌ NOT a password or secret
The certificate itself is completely public. Download any site's cert:
openssl s_client -connect google.com:443 </dev/null

The secret is the private key — it never leaves the server disk.
❌ NOT about encryption directly
The cert proves identity. Actual encryption uses session keys derived during the TLS handshake. Without identity verification you'd be encrypting to an unknown party — a MITM could intercept.
✓ What it actually does
Binds a domain name to a public key, with a CA vouching for the binding. Client uses the public key to verify the server's identity during TLS handshake. That's the whole job.
🛂 The Passport Analogy
A cert is like a passport. It says "this server is api.example.com" and is stamped by a trusted government (CA).

You trust the passport because you trust the issuing government — you don't need to have met the server before.

The private key is like the passport holder's face — only they have it, and it matches the photo (public key) on the passport.

Your OS ships with ~150 pre-installed Root CA certs. Every website's cert traces back to one of them. You never manually trust each site — you trust the root, which vouches for intermediates, which vouch for leaf certs.

Step 0 / 5
Root CA
🏛️ ISRG Root X1
Self-signed. Pre-installed in your OS.
Valid: 2015–2035 · Subject == Issuer
↑ signed by
Intermediate
🔗 Let's Encrypt R10
Cross-signed by ISRG Root X1.
Issuer: ISRG Root X1 · Valid: 90d rotated
↑ signed by
Leaf cert
📄 api.example.com
Signed by Let's Encrypt R10.
Issuer: Let's Encrypt R10 · Valid: 90d
Browser verification log
Press Next to walk through browser verification…
Why not just trust leaf certs directly?
Root CAs are stored offline in HSMs for security — they can't be available to sign millions of certs. Intermediates act as proxies: Root signs 1 intermediate, intermediate signs millions of leaf certs. If an intermediate is compromised, it can be revoked without touching the root.

certbot automates the ACME protocol. The core insight: the CA never needs to trust you personally — it just proves you control the domain, then signs your public key for that domain.

Step 0 / 8
🖩
Your Server
certbot running
No cert yet
🏛️
Let's Encrypt
ACME API
CA online
🌐
DNS / HTTP
Challenge target
Unchallenged
ACME certificate issuance log
Press Next to walk through cert issuance…
HTTP-01 vs DNS-01 Challenge
HTTP-01: CA fetches a token from
http://yourdomain/.well-known/acme-challenge/TOKEN
Simple. Can't issue wildcards. Port 80 must be reachable.

DNS-01: Add TXT record
_acme-challenge.yourdomain = TOKEN
Works for wildcards (*.example.com). Works behind firewalls. Required for Cloudflare Workers.
K3s homelab (cert-manager)
cert-manager + Let's Encrypt + Cloudflare DNS-01:

① cert-manager watches for Certificate CRDs
② Calls LE ACME API → gets challenge token
③ Creates DNS TXT record via Cloudflare API
④ LE verifies DNS → signs CSR → returns cert
⑤ cert-manager stores cert in K8s Secret
⑥ Ingress reads Secret → serves cert
⑦ Auto-renews 30d before expiry

certbot places files in specific directories. The server is configured to read them on startup. Here is exactly where everything goes and what each file is.

Linux / nginx
# certbot places files automatically: /etc/letsencrypt/live/api.example.com/ fullchain.pem ← cert + intermediates privkey.pem ← private key (600 perms) cert.pem ← leaf cert only chain.pem ← intermediates only # nginx reads them: server { listen 443 ssl; ssl_certificate /etc/letsencrypt/.../fullchain.pem; ssl_certificate_key /etc/letsencrypt/.../privkey.pem; } ⚠ Always use fullchain.pem # cert.pem alone = broken chain
Kubernetes (cert-manager)
# cert-manager auto-creates K8s Secret. # Or manually from files: kubectl create secret tls api-tls \ --cert=fullchain.pem \ --key=privkey.pem # Secret stored in etcd (encrypted) # Reference in Ingress: apiVersion: networking.k8s.io/v1 kind: Ingress spec: tls: - hosts: [api.example.com] secretName: api-tls # cert-manager ClusterIssuer: # Watches Certificate CRDs # Calls LE ACME API # Auto-renews 30d before expiry
Cloudflare (your setup)
# Edge cert: Cloudflare manages fully Edge cert: auto-managed ✓ Renewal: auto ✓ Install: zero steps ✓ # Origin cert (CF → your server): # Option A: Let's Encrypt on origin # Option B: Cloudflare Origin Cert Dashboard → SSL/TLS → Origin Server → Create Certificate → Download # Origin cert valid 15 years # Only trusted by Cloudflare edge # Browsers never see origin cert # (they terminate at CF edge)
K3s homelab
cert-manager + Let's Encrypt + Cloudflare DNS-01 challenge. cert-manager handles CSR, renewal, and K8s Secret injection. Set up once — never touch again.
Client trust stores — where pre-installed root CAs live
PlatformWhere roots are storedAdd custom roots?
macOSKeychain Access → System RootsYes — drag cert into Keychain → Always Trust
Linux/etc/ssl/certs/ · /usr/share/ca-certificates/Yes — copy to /usr/local/share/ca-certificates/ → update-ca-certificates
Windowscertmgr.msc → Trusted Root CAsYes — certmgr or Group Policy
iOSSettings → General → About → Certificate Trust SettingsYes — install profile via Settings → VPN and Device Management
K8s pods/etc/ssl/certs/ on node (inherited by pods)Yes — inject custom CA via ConfigMap mount, or cert-manager CA injector

The full life of a certificate — from key generation to revocation. If automation is correct, you never manually touch any of this.

Step 0 / 8
🔑
Key Generation
certbot generates ECDSA private key. Stored locally. Never transmitted.
📝
CSR Created
Public key + domain info packaged into CSR. Sent to Let's Encrypt.
🧪
Domain Validated
CA proves you control the domain via HTTP-01 or DNS-01 challenge.
✍️
CA Signs Cert
CA hashes CSR, signs with CA private key → signature appended.
📦
Deployed
cert + key placed on server or in K8s Secret. nginx/Ingress configured.
🔄
Auto-Renewal (day 60)
certbot/cert-manager re-runs steps 1–4. Old cert stays active until swap.
⚠️
Expiry (day 90)
If renewal failed — browsers reject instantly. Monitor at 30d + 7d.
🗑️
Revocation
Key compromised → CA marks cert revoked in OCSP. Browsers check before trusting.
Renewal commands
# certbot installs a systemd timer automatically systemctl status certbot.timer # Runs twice daily — renews if < 30d to expiry # Test renewal works (dry run): certbot renew --dry-run # Force-renew now: certbot renew --force-renewal # Check expiry on live server: openssl s_client -connect api.example.com:443 \ 2>/dev/null | openssl x509 -noout -dates # Revoke if key compromised: certbot revoke --cert-path \ /etc/letsencrypt/live/.../cert.pem
Expiry monitoring runbook
Days leftActionSeverity
90 → 31Nothing. certbot handles renewal automatically.OK
30Alert fires. Investigate if certbot hasn't renewed.WARN
7Page on-call. Something broken — manual intervention likely needed.CRITICAL
0Site down. Emergency: certbot renew --force-renewal + nginx reload.INCIDENT
Key leakedRevoke immediately. Re-issue with fresh key. Rotate all secrets.INCIDENT

How does a browser trust a server it has never spoken to before? No shared secret, no prior relationship, no manual setup needed. The chain of trust bootstraps through your OS.

Step 0 / 7
🖥️
Client
~150 root CAs
pre-installed in OS
Has OS trust store
🖩
Server
Has cert + key
Never met client
Has cert+key
🏛️
Root CA
ISRG Root X1
Pre-installed
Pre-trusted in OS
First connection trace
Press Next to trace the very first connection…
The Bootstrap Problem
How can you trust a server you've never met? You can't directly. You trust your OS maker (Apple, Microsoft, Google) who vetted ~150 Root CAs and pre-installed them. Those CAs verify domain ownership before signing. So: your trust in a website ultimately rests on trusting your OS maker.
Why this works without central coordination
The Root CA public keys in your OS are the only pre-shared secrets needed. Everything else — which CAs exist, which domains have certs, which keys are valid — is determined at connection time via cryptographic verification. Zero central directory needed.

A cert proves "this public key belongs to api.example.com". But how does the client verify the server actually holds the matching private key? Via the CertificateVerify message — a digital signature over the handshake transcript.

Step 0 / 8
🖥️
Client
Wants to verify
server identity
Verifying…
🖩
Server
Has cert + private key
Must prove identity
Ready to prove
Identity proof log
Press Next to see how identity proof works…
CertificateVerify — the math
handshake transcript→ SHA-256 →hash H
hash H+ server private key →signature S
S+ cert public key →H'
H'==H?✓ IDENTITY PROVEN
Only the entity with the private key could produce S. No private key was transmitted. Math proves possession.
Why a MITM can't spoof this
A MITM can intercept and forward the cert (it's public). But they cannot produce a valid CertificateVerify signature because they don't have the private key.

The client verifies S using the cert's public key. Invalid S → handshake fails → connection aborted. The cert and the private key proof are inseparable — you need both.

In regular TLS only the server authenticates. In mTLS both sides do. Used inside Kubernetes so services can't impersonate each other even if the cluster network is compromised.

Step 0 / 7
⚙️
Service A
user-service
Client cert
Has cert
⚙️
Service B
order-service
Server cert
Has cert
🔐
Citadel
Istio internal CA
Signs both
Issues 24h certs
mTLS mutual auth flow
Press Next to see mutual auth between two pods…
How internal certs are issued
Istio Citadel is an internal CA. On cluster startup it generates a root cert. On pod startup it issues each pod a short-lived (24h) cert with a SPIFFE identity. All pods trust the same internal root. Certs auto-rotate — app code never touches any of it.
SPIFFE ID format
spiffe://cluster.local/ns/default/sa/user-service

Encodes: cluster, namespace, service account. Gives each pod a cryptographic identity. AuthorizationPolicy: "only user-service may call order-service".
Regular TLS vs mTLS
AspectTLSmTLS
Server auth
Client auth✗ anonymous✓ cert required
Client needs certNoYes
App code changes?NoNo — Envoy sidecar handles it
Use casePublic webService mesh, zero-trust